﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Web7
{
    /// <summary>
    /// Objects implementing <see cref="AbstractEquatableObject{T}"/> are provided with common functionality for establishing domain-specific equality
    /// and a robust implementation of GetHashCode
    /// </summary>
    /// <typeparam name="T"></typeparam>
    [Serializable]
    public abstract class AbstractEquatableObject<T> where T : AbstractEquatableObject<T>
    {
        /// <summary>
        /// Returns the real type in case the <see cref="object.GetType" /> method has been proxied.
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        protected internal virtual Type GetNativeType()
        {
            // Returns the real type in case the GetType method has been proxied
            // See http://groups.google.com/group/sharp-architecture/browse_thread/thread/ddd05f9baede023a for clarification
            return GetType();
        }


        protected internal virtual int GetInstanceHashcode()
        {
            return base.GetHashCode();
        }

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="left">The left.</param>
        /// <param name="right">The right.</param>
        /// <returns>The result of the operator.</returns>
        /// <remarks></remarks>
        public static bool operator ==(AbstractEquatableObject<T> left, AbstractEquatableObject<T> right)
        {
            // If both are null, or both are same instance, return true.
            if (ReferenceEquals(left, right)) return true;

            // If one is null, but not both, return false.
            if (((object)left == null) || ((object)right == null)) return false;

            return left.Equals(right);
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="left">The left.</param>
        /// <param name="right">The right.</param>
        /// <returns>The result of the operator.</returns>
        /// <remarks></remarks>
        public static bool operator !=(AbstractEquatableObject<T> left, AbstractEquatableObject<T> right)
        {
            return !(left == right);
        }

        /// <summary>
        /// A static <see cref="ConcurrentDictionary{Type, IEnumerable{PropertyInfo}}"/> cache of natural ids for types which may implement this abstract class.
        /// </summary>
        protected readonly static ConcurrentDictionary<Type, IEnumerable<PropertyInfo>> EqualityComparisonMemberCache = new ConcurrentDictionary<Type, IEnumerable<PropertyInfo>>();

        /// <summary>
        /// Gets the natural id members.
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        protected abstract IEnumerable<PropertyInfo> GetMembersForEqualityComparison();

        /// <summary>
        /// Ensures the natural id members are cached in the static <see cref="EqualityComparisonMemberCache"/>.
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        protected internal virtual IEnumerable<PropertyInfo> EnsureEqualityComparisonMembersCached()
        {
            // The delegate inside this cache item is intentionally enumerated by calling ToArray otherwise the caching
            // is a little moot as it will be enumerated each time, and if an inheritor is using expression walking
            // it will get evaluated every time.
            return EqualityComparisonMemberCache.GetOrAdd(GetNativeType(), x => GetMembersForEqualityComparison().ToArray());
        }
    }
}